package com.jadows.tdog.optional.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ConcurrentHashSet;
import com.jadows.tdog.optional.annotations.OpImpl;
import com.jadows.tdog.optional.annotations.OpInject;
import com.jadows.tdog.optional.model.OpBean;
import com.jadows.tdog.optional.model.TargetWrapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.core.StandardReflectionParameterNameDiscoverer;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.lang.NonNull;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.util.*;
import java.util.stream.Collectors;

import static org.springframework.core.annotation.AnnotationUtils.getAnnotation;

/**
 * @author taojiacheng
 */
@Slf4j
@Configuration
public class OpAnnotationBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware, ApplicationListener<ContextRefreshedEvent> {


    public final Set<OpBean> opBeanSet = new ConcurrentHashSet<>();
    public ConfigurableListableBeanFactory beanFactory;

    /**
     * 创建代理工厂对象
     *
     * @param beanFactory beanFactory
     * @param type        type spring bean 中 OpInject 注入的类型
     */
    static Object createProxy(ConfigurableListableBeanFactory beanFactory, Class<?> type) {
        Map<String, ?> beansOfType = beanFactory.getBeansOfType(type);
        if (beansOfType.isEmpty()) {
            throw new IllegalArgumentException("No bean of type " + type.getName() + " found");
        }
        Map<String, List<TargetWrapper>> implWrappers = new LinkedHashMap<>(10);
        for (Object bean : beansOfType.values()) {
            // 取出所有 OpImpl 的方法
            ReflectionUtils.doWithLocalMethods(bean.getClass(), method -> {
                OpImpl annotation = getAnnotation(method, OpImpl.class);
                if (annotation == null) {
                    return;
                }
                String condition = annotation.condition();
                int priority = annotation.priority();
                TargetWrapper target = new TargetWrapper(bean, method, condition, priority);
                List<TargetWrapper> targets = implWrappers.get(method.getName());
                if (targets == null) {
                    targets = new ArrayList<>();
                }
                targets.add(target);
                implWrappers.put(method.getName(), targets);
            });
        }
        // 按照优先级排序
        sortOpMethodMap(implWrappers);
        return Proxy.newProxyInstance(type.getClassLoader(), new Class[]{type}, (proxy, method, args) -> {
            // Object 中的方法直接执行 [toString hashCode equals 等]
            if (Object.class.equals(method.getDeclaringClass())) {
                return invokeMethod(new Object(), method, args);
            }
            List<TargetWrapper> wrappers = filterTargets(
                    implWrappers.getOrDefault(method.getName(), Collections.emptyList()), args);
            if (CollUtil.isEmpty(wrappers)) {
                if (beansOfType.size() == 1) {
                    // 仅有一个实现 执行默认实现
                    return invokeMethod(beansOfType.values().iterator().next(), method, args);
                }
                // 有多个实现 但是未指定 @OpImpl
                // 输出警告日志，取优先级最高的实现
                log.warn("No @OpImpl found for method {}", method.getName());
                return invokeMethod(beansOfType.values().iterator().next(), method, args);
            }
            // 按照优先级计算el表达式
            for (TargetWrapper target : wrappers) {
                Boolean result = false;
                try {
                    result = parseCondition(target, beanFactory, args);
                } catch (Exception e) {
                    log.error("parse condition error, {}", e.getMessage(), e);
                }
                if (BooleanUtils.isTrue(result)) {
                    return invokeMethod(target.getBean(), method, args);
                }
            }
            // 未找到符合条件的实现, 取优先级最高的实现
            log.warn("unmatched condition for method {}, params: {}", method.getName(), Arrays.toString(args));
            return invokeMethod(wrappers.get(0).getBean(), method, args);
        });
    }

    /**
     * 执行目标方法
     */
    static Object invokeMethod(Object bean, Method method, Object[] args) throws Throwable {
        try {
            return method.invoke(bean, args);
        } catch (InvocationTargetException e) {
            throw e.getCause();
        }
    }

    /**
     * 兼容方法重载, 过滤出符合条件的目标
     */
    static List<TargetWrapper> filterTargets(List<TargetWrapper> targets, Object[] args) {
        if (args == null) {
            args = new Object[0];
        }
        Object[] finalArgs = args;
        return targets.stream().filter(target -> {
            // 参数个数不一致
            if (target.getMethod().getParameterCount() != finalArgs.length) {
                return false;
            }
            // 参数类型不一致
            Class<?>[] parameterTypes = target.getMethod().getParameterTypes();
            for (int i = 0; i < parameterTypes.length; i++) {
                if (finalArgs[i] == null) {
                    continue;
                }
                if (!parameterTypes[i].isAssignableFrom(finalArgs[i].getClass())) {
                    return false;
                }
            }
            return true;
        }).collect(Collectors.toList());
    }

    /**
     * 按照优先级排序
     */
    static void sortOpMethodMap(Map<String, List<TargetWrapper>> opMethodMap) {
        opMethodMap.forEach((key, value) -> {
            value.sort((o1, o2) -> {
                if (o1.getPriority() == o2.getPriority()) {
                    return 0;
                }
                return o1.getPriority() > o2.getPriority() ? 1 : -1;
            });
        });
    }

    static Boolean parseCondition(TargetWrapper target, ConfigurableBeanFactory beanFactory, Object[] args) {
        // 获取被拦截方法参数名列表 (使用Spring支持类库)
        StandardReflectionParameterNameDiscoverer discoverer = new StandardReflectionParameterNameDiscoverer();
        // 方法上下文
        StandardEvaluationContext context = new StandardEvaluationContext();
        context.setBeanResolver(new BeanFactoryResolver(beanFactory));
        // 把方法参数放入 spEl 上下文中
        String[] paraNameArr = discoverer.getParameterNames(target.getMethod());
        if (paraNameArr != null && paraNameArr.length > 0) {
            for (int i = 0; i < paraNameArr.length; i++) {
                context.setVariable(paraNameArr[i], args[i]);
            }
        }
        String condition = target.getCondition();
        condition = beanFactory.resolveEmbeddedValue(condition);
        if (StringUtils.isEmpty(condition)) {
            return false;
        }
        SpelExpressionParser parser = new SpelExpressionParser();
        Expression expression = parser.parseExpression(condition);
        return expression.getValue(context, Boolean.class);
    }

    @Override
    public void setBeanFactory(@NonNull BeanFactory beanFactory) throws BeansException {
        if (!(beanFactory instanceof ConfigurableListableBeanFactory)) {
            throw new IllegalArgumentException(
                    "OpAnnotationBeanPostProcessor requires a ConfigurableListableBeanFactory");
        }
        this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
    }

    @Override
    public Object postProcessBeforeInitialization(@NonNull Object bean, @NonNull final String beanName) throws BeansException {
        ReflectionUtils.doWithFields(bean.getClass(), field -> {
            OpInject annotation = getAnnotation(field, OpInject.class);
            int modifiers = field.getModifiers();
            if (annotation == null || Modifier.isFinal(modifiers)) {
                return;
            }
            OpBean opBean = new OpBean(bean, field);
            opBeanSet.add(opBean);
        });
        return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
    }

    @Override
    public void onApplicationEvent(@NonNull ContextRefreshedEvent event) {
        // 属性注入
        for (OpBean opBean : opBeanSet) {
            log.info("inject field {} for bean {}", opBean.getField().getName(), opBean.getBean().getClass().getName());
            ReflectionUtils.makeAccessible(opBean.getField());
            ReflectionUtils.setField(
                    opBean.getField(), opBean.getBean(), createProxy(beanFactory, opBean.getField().getType()));
        }
    }

}